﻿using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Caching;
using NLog;
using Yahoo.Yui.Compressor;

namespace Softio.Mscd
{
    public static class HandlerHelper
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        private const string AcceptEncodingHeader = "Accept-Encoding";
        private const string ContentLengthHeader = "Content-Length";
        private const string ContentEncodingHeader = "Content-Encoding";
        private const string IfModifiedSinceHeader = "If-Modified-Since";

        private const string DoNotMinimalizeJavaScriptKey = ".!min.";
        private const string ProxyMustRevalidate = "must-revalidate, proxy-revalidate";
        private const string GZipEncoding = "GZIP";
        private const string DeflateEncoding = "DEFLATE";
        private const string InternetExplorerBrowser = "IE";


        public static CacheContent GetResourceContent(HttpContext context, string cacheKey, string physicalFilePath, ResponseCompressionType compressionType, DateTime lastModified)
        {
            CacheContent cc = context.Cache[cacheKey] as CacheContent;
            if (cc == null)
            {
                cc = new CacheContent(File.ReadAllBytes(physicalFilePath), lastModified);

                context.Cache.Insert(
                    cacheKey,
                    cc,
                    new CacheDependency(physicalFilePath),
                    DateTime.Now.Add(HandlerConfig.DefaultCacheDuration),
                    Cache.NoSlidingExpiration);
            }
            return cc;
        }

        public static ResponseCompressionType GetCompressionFromExtension(HttpRequest request, string fileExtension)
        {
            if (Array.BinarySearch<string>(HandlerConfig.CompressFileTypes, fileExtension) < 0)
            {
                return ResponseCompressionType.None;
            }
            else
            {
                return GetCompressionMode(request);
            }
        }

        public static void WriteContent(HttpContext context, CacheContent cc, ResponseCompressionType compressionType, string contentType, bool useExpiredHeaders)
        {
            byte[] content = cc.GetContentByType(compressionType);

            ProduceResponseHeader(
                context.Response,
                content.Length,
                compressionType,
                cc.LastModifiedUtc,
                contentType,
                useExpiredHeaders);

            context.Response.OutputStream.Write(content, 0, content.Length);
        }

        public static CacheContent GetPackageContent(HttpContext context, Package package, string cacheKey, string physicalFilePath)
        {
            CacheContent cc = context.Cache[cacheKey] as CacheContent;
            if (cc == null)
            {
                string[] files = Directory.GetFiles(physicalFilePath);
                StringBuilder data = new StringBuilder(100000);
                foreach (string f in files.OrderBy(x => x))
                {
                    if (package.Type == PackageType.Style)
                    {
                        //http://www.codeplex.com/YUICompressor
                        data.AppendLine(
                            PrepareImagesVersion(
                            CssCompressor.Compress(
                                File.ReadAllText(f, HandlerConfig.FilesEncoding),
                                0,
                                CssCompressionType.Hybrid,
                                true),
                            f));
                    }
                    else
                    {
                        if (f.Contains(DoNotMinimalizeJavaScriptKey))
                        {
                            data.AppendLine(File.ReadAllText(f, HandlerConfig.FilesEncoding));
                        }
                        else
                        {

                            data.AppendLine(JavaScriptCompressor.Compress(
                                File.ReadAllText(f, HandlerConfig.FilesEncoding),
                                true,
                                true,
                                false,
                                false,
                                0,
                                HandlerConfig.FilesEncoding,
                                System.Globalization.CultureInfo.InvariantCulture));
                        }
                    }
                }
                DirectoryInfo di = new DirectoryInfo(physicalFilePath);
                var lastWriteTimeUtc = new DateTime(
                    di.LastWriteTimeUtc.Ticks - (di.LastWriteTimeUtc.Ticks % TimeSpan.TicksPerSecond),
                    di.LastWriteTimeUtc.Kind
                );

                cc = new CacheContent(
                    HandlerConfig.FilesEncoding.GetBytes(data.ToString()),
                    lastWriteTimeUtc);
                
                data = null;

                context.Cache.Insert(
                    cacheKey,
                    cc,
                    new CacheDependency(physicalFilePath),
                    DateTime.Now.Add(HandlerConfig.DefaultCacheDuration),
                    Cache.NoSlidingExpiration);
            }
            return cc;
        }

        private static string PrepareImagesVersion(string css, string fileName)
        {
            FileInfo fi = new FileInfo(fileName);
            string hash = fi.LastWriteTimeUtc.GetHashCode().ToString("X").ToLowerInvariant();
            StringBuilder output = new StringBuilder(css);
            output.Replace(".jpg", "_v" + hash + ".jpg");
            output.Replace(".jpeg", "_v" + hash + ".jpeg");
            output.Replace(".png", "_v" + hash + ".png");
            output.Replace(".gif", "_v" + hash + ".gif");
            return output.ToString();
        }

        public static bool IsKnownFile(string fileExtension)
        {
            return Array.BinarySearch<string>(HandlerConfig.FileTypes, fileExtension) >= 0;
        }

        public static Package TryGetPackage(HttpContext context)
        {
            string path = context.Request.AppRelativeCurrentExecutionFilePath; 
            if (path.StartsWith(String.Format("~/{0}styles/", HandlerConfig.RootDirectory), StringComparison.OrdinalIgnoreCase))
            {
                if (!path.EndsWith(".css", StringComparison.OrdinalIgnoreCase))
                {
                    return null;
                }
                return new Package(PackageType.Style);
            }
            else if (path.StartsWith(String.Format("~/{0}scripts/", HandlerConfig.RootDirectory), StringComparison.OrdinalIgnoreCase))
            {
                if (!path.EndsWith(".js", StringComparison.OrdinalIgnoreCase))
                {
                    return null;
                }
                return new Package(PackageType.Script);
            }
            return null;
        }

        public static DateTime? GetModifiedSinceHeaderValue(HttpContext context)
        {
            string headerDateText = context.Request.Headers[IfModifiedSinceHeader];
            if (!String.IsNullOrEmpty(headerDateText))
            {
                string[] modsince = headerDateText.Split(';');
                DateTime headerDate;
                if (modsince.Length >= 1 && DateTime.TryParse(modsince[0], null, System.Globalization.DateTimeStyles.AdjustToUniversal, out headerDate))
                {
                    return headerDate;
                }
            }
            return null;
        }

        public static void EnsureProperRequest(HttpRequest request)
        {
            if (request.FilePath.EndsWith(".asp", StringComparison.OrdinalIgnoreCase))
            {
                throw new HttpException((int)HttpStatusCode.Forbidden, "Path forbidden");
            }
            else if (request.FilePath.EndsWith(".aspx", StringComparison.OrdinalIgnoreCase))
            {
                throw new HttpException((int)HttpStatusCode.Forbidden, "Path forbidden");
            }
        }

        private static void WriteResponse(HttpResponse response, byte[] bytes)
        {
            response.OutputStream.Write(bytes, 0, bytes.Length);
            response.OutputStream.Flush();
        }

        public static bool ResponseModifiedSince(
            HttpResponse response,
            DateTime? incomingModifiedSinceUtc,
            DateTime lastModifiedUtc,
            bool useExpiredHeaders)
        {
            if (incomingModifiedSinceUtc.HasValue && incomingModifiedSinceUtc.Value.CompareTo(lastModifiedUtc) >= 0)
            {
                response.ClearHeaders();
                response.Cache.SetCacheability(HttpCacheability.Public);
                if (useExpiredHeaders && HandlerConfig.ExpiredDays != 0)
                {
                    response.Cache.SetMaxAge(TimeSpan.FromDays(HandlerConfig.ExpiredDays));
                    response.Cache.SetExpires(DateTime.Now.AddDays(HandlerConfig.ExpiredDays));
                }
                response.AppendHeader(ContentLengthHeader, "0");
                response.StatusCode = (int)HttpStatusCode.NotModified;
                return true;
            }
            return false;
        }

        public static void ProduceResponseHeader(
            HttpResponse response,
            int contentLength,
            ResponseCompressionType mode,
            DateTime lastModified,
            string contentType,
            bool useExpiredHeaders)
        {
            response.ClearHeaders();
            response.Cache.SetCacheability(HttpCacheability.Public);

            if (HandlerConfig.UseMustRevalidateHeader)
            {
                response.Cache.AppendCacheExtension(ProxyMustRevalidate);
            }

            if (useExpiredHeaders && HandlerConfig.ExpiredDays != 0)
            {
                response.Cache.SetMaxAge(TimeSpan.FromDays(HandlerConfig.ExpiredDays));
                response.Cache.SetExpires(DateTime.Now.AddDays(HandlerConfig.ExpiredDays));
            }
            response.Cache.SetLastModified(lastModified);
            response.ContentType = contentType;
            response.Charset = HandlerConfig.FilesEncoding.WebName;
            if (mode != ResponseCompressionType.None)
            {
                response.AppendHeader(ContentEncodingHeader, mode.ToString().ToLowerInvariant());
            }
            response.AppendHeader(ContentLengthHeader, contentLength.ToString());
        }

        public static ResponseCompressionType GetCompressionMode(HttpRequest request)
        {
            string serverProtocol = request.Params["SERVER_PROTOCOL"];
            if (serverProtocol != null && !serverProtocol.Contains("1.1"))
            {
                return ResponseCompressionType.None;
            }
            if (request.Browser.IsBrowser(InternetExplorerBrowser) && request.Browser.MajorVersion < 7)
            {
                return ResponseCompressionType.None;
            }

            string acceptEncoding = request.Headers[AcceptEncodingHeader];
            if (String.IsNullOrEmpty(acceptEncoding))
            {
                return ResponseCompressionType.None;
            }

            acceptEncoding = acceptEncoding.ToUpperInvariant();

            if (acceptEncoding.Contains(GZipEncoding))
            {
                return ResponseCompressionType.GZip;
            }
            else if (acceptEncoding.Contains(DeflateEncoding))
            {
                return ResponseCompressionType.Deflate;
            }
            else
            {
                return ResponseCompressionType.None;
            }
        }
    }
}